
#include <string.h>
#include <stdint.h>
#include "board.h"
#include "ev_trans.h"
#include "ev_tick_hook.h"

static uint32_t trans_tx_sem;
static uint32_t trans_rx_sem;
static ev_packet_t trans_rx_packet;
static volatile uint8_t trans_uart_rx_timer;
static ev_parser_t trans_parser;

static void wait_tx_done(void)
{
    uint32_t flag;
    while (1)
    {
        flag = driv_disable_irq();
        uint32_t tx_sem = trans_tx_sem;
        driv_enable_irq(flag);

        if (!tx_sem) {
            hw_delay_us(5);
        } else {
            break;
        }
    }
    flag = driv_disable_irq();
    trans_tx_sem = 0;
    driv_enable_irq(flag);
}

// _check_trans_rx_timeout is registered in tickhook where irq is already disabled
// so it need not to disable irq.
static void _check_trans_rx_timeout(void)
{
    // uint32_t level = driv_disable_irq();
    if (trans_uart_rx_timer > 1) {
        trans_uart_rx_timer --;
    } else if (trans_uart_rx_timer == 1) {
        ev_parser_reset(&trans_parser);
        trans_uart_rx_timer = 0;
    } else {
        trans_uart_rx_timer = 0;
    }
    // driv_enable_irq(level);
}

static void trans_parser_cb(const ev_parser_t *parser, const ev_packet_t *pkt)
{
    (void)parser;
    memcpy(&trans_rx_packet, pkt, sizeof(ev_packet_t));
    trans_rx_sem = 1;
}

int ev_trans_init(uint32_t baudrate)
{
    memset(&trans_rx_packet, 0, sizeof(ev_packet_t));
    trans_uart_rx_timer = 0;
    ev_tick_hook_register(&_check_trans_rx_timeout);
    ev_parser_init(&trans_parser, NULL, &trans_parser_cb);
    driv_trans_uart_init(baudrate);

    return 0;
}

int ev_trans_tx(const ev_packet_t *packet)
{
    static uint8_t tx_buf[EV_PACKET_MAX_LEN] __attribute__((aligned(8)));
    uint32_t tx_len;
    int ret = ev_packet_make(packet, tx_buf, &tx_len);
    if (ret != 0) {
        return -1;
    }
    if (tx_len > 0) {
        driv_trans_uart_write(tx_buf, tx_len);
        wait_tx_done();
    }
    return 0;
}

int ev_trans_rx(ev_packet_t *packet, int32_t timeout_ms)
{
    if (!packet) {
        return -1;
    }
    if (timeout_ms < 0) {
        timeout_ms = INT32_MAX;
    }
    uint32_t t = driv_tick_get();
    do {
        uint32_t level = driv_disable_irq();
        if (trans_rx_sem) {
            trans_rx_sem = 0;
            memcpy(packet, &trans_rx_packet, sizeof(ev_packet_t));
            driv_enable_irq(level);
            return 0;
        }
        driv_enable_irq(level);
        hw_delay_us(5);
    } while(driv_tick_elaps(t) < (uint32_t)timeout_ms);
    return -1;
}

// driver trans uart callback implement
void driv_trans_uart_tx_cb(void)
{
    uint32_t level = driv_disable_irq();
    trans_tx_sem = 1;
    driv_enable_irq(level);
}

void driv_trans_uart_rx_cb(uint8_t c)
{
    uint32_t level = driv_disable_irq();
    ev_parser_feed_one(&trans_parser, c);
    trans_uart_rx_timer = 5; // 5ms timeout
    driv_enable_irq(level);
}
